#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

#include "ipc.h"


static int    argc;
static char** argv;

/************************
*         UTIL          *
************************/
static int connectToDaemon( void ){
	
	int mySocket;
	struct sockaddr_un socketAddress;
	
	//make sure the SIGPIPE doesn't disturb us
	signal( SIGPIPE, SIG_IGN );
	
	mySocket = socket( AF_UNIX, SOCK_STREAM, 0 );
	if( mySocket < 0 ){
		perror( "[upekux] Create socket" );
		return -1;
	}
	
	memset( &socketAddress, 0, sizeof(struct sockaddr_un) );
	
	socketAddress.sun_family = AF_UNIX;
	strncpy( socketAddress.sun_path, IPC_SOCKET, sizeof(socketAddress.sun_path) - 1 );
	
	if( connect(mySocket, (const struct sockaddr*)&socketAddress, sizeof(struct sockaddr_un)) ){
		perror( "[upekux] Connect to daemon" );
		return -2;
	}
	
	if( checkSocketSecurity(mySocket) ){
		printf( "[upekux] Connection to daemon failed security check.\n" );
		fflush( stdout );
		close( mySocket );
		return -3;
	}
	
	return mySocket;
}

static int messageFromDriver( int socket ){
	
	unsigned char msg;
	
	if( recv(socket, &msg, 1, MSG_WAITALL) != 1 ) return 1;
	
	if(      msg == 0x07 ) printf( "[upekux] Finger shifted to the left. Swipe center.\n" );
	else if( msg == 0x08 ) printf( "[upekux] Finger shifted to the right. Swipe center.\n" );
	else if( msg == 0x0b ) printf( "[upekux] Bad quality. Swipe again.\n" );
	else if( msg == 0x0f ) printf( "[upekux] Wrong swipe. Swipe again.\n" );
	else if( msg == 0x1c ) printf( "[upekux] Swiped too fast. Swipe again.\n" );
	else if( msg == 0x1d ) printf( "[upekux] Finger shifted. Swipe center.\n" );
	else if( msg == 0x1e ) printf( "[upekux] Swiped too short. Swipe again.\n" );
	else if( msg == 0xff ) printf( "[upekux] Swipe scanner\n" );//message from ipcd
	else printf( "[upekux] WARNING: driver sends unknown message: 0x%02x\n", msg );
	
	fflush( stdout );//need in PAM case
	
	return 0;
}

/************************
*         USER          *
************************/

static int touchpad( void ){
	
	unsigned char cmd;
	int res;
	int mySocket;
	unsigned char ans;
	
	if( argc < 3 ){
		l_help:
		printf( "Usage: %s %s <command>\n\nCommand list:\n", argv[0], argv[1] );
		printf( "  status     Print current touchpad status\n" );
		printf( "  off        Disable touchpad mode\n" );
		printf( "  mouse      Enable touchpad in mouse mode\n" );
		printf( "  wheels     Enable touchpad in scrolling mode\n" );
		printf( "  arrows     Enable touchpad in arrowkeys mode\n" );
		return 2;
	}
	
	if(      !strcmp(argv[2], "status") ) cmd = IPC_TOUCHPAD_STATUS;
	else if( !strcmp(argv[2], "off"   ) ) cmd = IPC_TOUCHPAD_OFF;
	else if( !strcmp(argv[2], "mouse" ) ) cmd = IPC_TOUCHPAD_MOUSE;
	else if( !strcmp(argv[2], "wheels") ) cmd = IPC_TOUCHPAD_WHEELS;
	else if( !strcmp(argv[2], "arrows") ) cmd = IPC_TOUCHPAD_ARROWS;
	else goto l_help;
	
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	if( write(mySocket, &IPC_TOUCHPAD, 1) != 1 ) goto l_perror;
	if( write(mySocket, &cmd,          1) != 1 ) goto l_perror;
	
	if( recv(mySocket, &ans, 1, MSG_WAITALL) != 1 ) goto l_perror;
	
	res = 0;
	
	if(      ans == IPC_TOUCHPAD_OFF    ) printf( "Touchpad is off.\n" );
	else if( ans == IPC_TOUCHPAD_MOUSE  ) printf( "Touchpad in mouse mode.\n" );
	else if( ans == IPC_TOUCHPAD_WHEELS ) printf( "Touchpad in wheels mode.\n" );
	else if( ans == IPC_TOUCHPAD_ARROWS ) printf( "Touchpad in arrowkeys mode.\n" );
	else {
		res = 1;
		printf( "Operation failed or driver error.\n" );
	} 
	
	close( mySocket );
	return res;
	
l_perror:
	perror( "error" );
	close( mySocket );
	return 1;
}


/*
request:  <IPC_USER_AUTH> <type> <userlen(int)> <username>
response: <IPC_SUCCESS> || <IPC_ERROR> || <IPC_DRIVER_MESSAGE> <MSG>
          also messages from driver

if auth success MUST return 0
else MUST return NOT 0
*/
static int auth( void ){
	
	const char* resultMsg[2] = { "Auth success.", "Auth failed." };
	
	char* eRHOST;
	char* eRUSER;
	char* eSERVICE;
	char* eTTY;
	char* eUSER;
	char* eTYPE;
	
	char* username;
	int usernameLength;
	
	int res;
	int mySocket;
	unsigned char ans;
	unsigned char authMode;
	
	//print help if need
	if( argc < 3 ){
		l_help:
		printf( "This command used to authorize users.\n\n" );
		printf( "  1) Direct usage or in shell script:\n" );
		printf( "  %s %s <mode> <user>\n", argv[0], argv[1] );
		printf( "  Exit code 0 if auth success else failed.\n\n" );
		printf( "  2) Usage as the PAM module. Add next string in the desired PAM config:\n" );
		printf( "  auth   sufficient   pam_exec.so   stdout quiet   /path/to/upekux auth <mode>\n\n");
		printf( "<mode> must be set to one of the following values:\n");
		printf( "  active      Puts the scanner in scanning mode and waits for the swipe.\n" );
		printf( "              This mode has timeout 2 min. In this mode, the scanner is\n" );
		printf( "              constantly polled by the host.\n\n" );
		printf( "  passive     Puts the scanner in standby mode and waits for the swipe. This\n" );
		printf( "              mode has no timeout. In this mode, the host waits for interrupt\n" );
		printf( "              from the scanner. The program in <action> will be executed when\n" );
		printf( "              the fingerprint of another user is detected in this mode.\n" );
		fflush( stdout );
		return 2;
	}
	
	//try read PAM environment variables
	eRHOST   = getenv( "PAM_RHOST"   );
	eRUSER   = getenv( "PAM_RUSER"   );
	eSERVICE = getenv( "PAM_SERVICE" );
	eTTY     = getenv( "PAM_TTY"     );
	eUSER    = getenv( "PAM_USER"    );
	eTYPE    = getenv( "PAM_TYPE"    );
	
	//if PAM variables detected
	if( eRHOST || eRUSER || eSERVICE || eTTY || eUSER || eTYPE ){
		
		if( argc > 3 ) goto l_help;
		
		if( !eUSER || !eTYPE ){
			printf( "[upekux] PAM_USER and PAM_TYPE must be set.\n" );
			fflush( stdout );
			return 2;
		}
		
		if( strcmp(eTYPE, "auth") ){
			printf( "[upekux] PAM_TYPE \"%s\" not supported\n", eTYPE );
			fflush( stdout );
			return 2;
		}
		
		username = eUSER;
		
	}else if( argc < 4 ) goto l_help;
	else username = argv[3];
	
	//calculate and check username length
	usernameLength = strlen( username );
	if( usernameLength < 1 || usernameLength > 32 ){
		printf( "[upekux] Username length must be 1-32.\n" );
		fflush( stdout );
		return 2;
	}
	
	//select auth type
	if(      !strcmp("active",  argv[2]) ) authMode = IPC_USER_AUTH_ACTIVE;
	else if( !strcmp("passive", argv[2]) ) authMode = IPC_USER_AUTH_PASSIVE;
	else goto l_help;
	
	//connect to daemon
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	//send auth request, type, username length and username
	if( write(mySocket, &IPC_USER_AUTH,  1                     ) != 1                      ) goto l_perror;
	if( write(mySocket, &authMode,       1                     ) != 1                      ) goto l_perror;
	if( write(mySocket, &usernameLength, sizeof(usernameLength)) != sizeof(usernameLength) ) goto l_perror;
	if( write(mySocket, username,        usernameLength        ) != usernameLength         ) goto l_perror;
	
l_readAns:
	//read response
	if( recv(mySocket, &ans, 1, MSG_WAITALL) != 1 ) goto l_perror;
	
	//print message from driver
	if( ans == IPC_DRIVER_MESSAGE ){
		if( messageFromDriver(mySocket) ) goto l_perror;
		goto l_readAns;
	}
	
	//handle response
	if( ans == IPC_SUCCESS ) res = 0;
	else res = 1;
	
	//print auth result
	printf( "[upekux] %s\n", resultMsg[res] );
	fflush( stdout );
	goto l_exit;
	
l_perror:
	res = 1;
	perror( "[upekux] error" );
	
l_exit:
	close( mySocket );
	return res;
}

/*
proto:
	<cmd>
	<id> <userlen> [user] <descriptionlen> [description]
	...
	<0>
*/
static int fingerList( void ){
	
	int res;
	int mySocket;
	int id;
	char buf[1024];
	int len;
	int i;
	
	res = 0;
	
	//connect to daemon
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	//send cmd
	if( write(mySocket, &IPC_FINGER_LIST, 1) != 1 ) goto l_perror;
	
	//read cmd
	if( recv(mySocket, buf, 1, MSG_WAITALL) != 1 ) goto l_perror;
	if( buf[0] != IPC_FINGER_LIST ){
		printf( "Received wrong data\n" );
		res = 1;
		goto l_close;
	}
	
	printf( "<ID>\t<USER>\t\t<DESCRIPTION>\n");
	
l_readNext:
	//read id
	if( recv(mySocket, &id, sizeof(id), MSG_WAITALL) != sizeof(id) ) goto l_perror;
	if( !id ) goto l_close;//end of list
	printf( "%i\t", id );
	
	for( i = 0; i < 2; i++ ){
		//field len
		if( recv(mySocket, &len, sizeof(len), MSG_WAITALL) != sizeof(len) ) goto l_perror;
		if( len > 0 ){
			if( len > 1023 ){
				printf( "Received data too big\n" );
				res = 1;
				goto l_close;
			}
			//field content
			if( recv(mySocket, buf, len, MSG_WAITALL) != len ) goto l_perror;
			buf[len] = 0x00;//make sure string is null-terminated
			printf( "%s\t\t", buf );
		} else printf( "<EMPTY>\t\t" );
	}
	printf( "\n" );
	goto l_readNext;
	
	//close connection and return
l_close:
	close( mySocket );
	return res;
	
l_perror:
	perror( "error" );
	close( mySocket );
	return 1;
}

/*
request: IPC_FINGER_DETECT
answer:  IPC_FINGER_DETECT <id(int)> || IPC_ERROR
         also messages from driver
*/
static int fingerDetect( void ){
	
	int res;
	int mySocket;
	unsigned char ans;
	int id;
	
	//connect to daemon
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	//send command
	if( write(mySocket, &IPC_FINGER_DETECT, 1) != 1 ) goto l_perror;
	
l_recv:
	//read answer
	if( recv(mySocket, &ans, 1, MSG_WAITALL) != 1 ) goto l_perror;
	
	//print driver message
	if( ans == IPC_DRIVER_MESSAGE ){
		if( messageFromDriver(mySocket) ) goto l_perror;
		goto l_recv;
	}
	
	//print command result
	if( ans == IPC_FINGER_DETECT ){
		if( recv(mySocket, &id, sizeof(id), MSG_WAITALL) != sizeof(id) ) goto l_perror;
		
		if( id ) printf( "Found match with finger with id: %i\n", id );
		else     printf( "No match found.\n" );
		res = 0;
	}else{
		printf( "Operation failed.\n" );
		res = 1;
	}
	
	close( mySocket );
	return res;
	
l_perror:
	perror( "error" );
	close( mySocket );
	return 1;
}

/***********************
*        ADMIN         *
***********************/

/*
request: <IPC_FINGER_ADD> <MODE>
answer
	<IPC_FINGER_ADD> <int>
	<IPC_SUCCESS> <ID>
	<IPC_ERROR>
	also messages from driver
*/
static int addNewFingerprint( void ){
	
	int mySocket;
	unsigned char mode;
	unsigned char ans;
	int tmp;
	int new_id;
	int res;
	
	//print help is need
	if( argc < 3 ){
		l_help:
		printf( "Usage: %s %s <mode> \n\nMode list:\n", argv[0], argv[1] );
		printf( "  single     Scan finger once and register him.\n" );
		printf( "  best       Scan finger several times and register the best fingerprint.\n" );
		return 2;
	}
	
	if(      !strcmp(argv[2], "single") ) mode = IPC_FINGER_ADD_SINGLE;
	else if( !strcmp(argv[2], "best"  ) ) mode = IPC_FINGER_ADD_BEST;
	else goto l_help;
	
	//connect to daemon
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	//send command and mode
	if( write(mySocket, &IPC_FINGER_ADD, 1) != 1 ) goto l_perror;
	if( write(mySocket, &mode,           1) != 1 ) goto l_perror;
	
l_read:
	//read answer
	if( recv(mySocket, &ans, 1, MSG_WAITALL) != 1 ) goto l_perror;
	
	//print message from driver
	if( ans == IPC_DRIVER_MESSAGE ){
		if( messageFromDriver(mySocket) ) goto l_perror;
		goto l_read;
	}
	
	//print integer
	if( ans == IPC_FINGER_ADD ){
		if( recv(mySocket, &tmp, sizeof(tmp), MSG_WAITALL) != sizeof(tmp) ) goto l_perror;
		printf( "Scan result: %i\n", tmp );
		goto l_read;
	}
	
	res = 1;
	
	//print operation result
	if( ans == IPC_SUCCESS ){
		if( recv(mySocket, &new_id, sizeof(new_id), MSG_WAITALL) != sizeof(new_id) ) goto l_perror;
		res = 0;
		printf( "New finger has been registred width id %i\n", new_id );
	}else if(ans == IPC_DENIED ) printf( "Access denied.\n" );
	else printf( "Operation failed\n" );
	
	close( mySocket );
	return res;
	
l_perror:
	perror( "error" );
	close( mySocket );
	return 1;
}

/*
proto
	<IPC_SUCCESS> or <IPC_ERROR>
	
	<len> [data] user
	<len> [data] desc
	<len> [data] threshold
	<len> [data] action

*/
static int viewFinger( void ){
	
	int res;
	int i;
	int id;
	int mySocket;
	int len;
	char buf[1024];
	
	const char fields[4][12] = { "User", "Description", "Threshold", "Action" };
	
	if( argc < 3 ){
		l_help:
		printf( "Usage: %s %s <id>\n", argv[0], argv[1] );
		return 2;
	}
	
	if( sscanf(argv[2], "%i", &id) != 1 ) goto l_help;
	
	res = 0;
	
	//connect to daemon
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	//send command and id 
	if( write(mySocket, &IPC_FINGER_VIEW, 1         ) != 1          ) goto l_perror;
	if( write(mySocket, &id,              sizeof(id)) != sizeof(id) ) goto l_perror;
	
	//read answer
	if( recv(mySocket, buf, 1, MSG_WAITALL) != 1 ) goto l_perror;
	
	if( buf[0] == IPC_DENIED ){
		printf( "Access denied.\n" );
		res = 1;
		goto l_close;
	}else if( buf[0] != IPC_SUCCESS ){
		printf( "Operation failed\n" );
		res = 1;
		goto l_close;
	}
	
	printf( "Info about record with id %i:\n", id );
	
	for( i = 0; i < 4; i++ ){
		if( recv(mySocket, &len, sizeof(len), MSG_WAITALL) != sizeof(len) ) goto l_perror;
		if( len > 0 ){
			if( len > 1023 ){
				printf( "Received data too big\n" );
				res = 1;
				goto l_close;
			}
			if( recv(mySocket, buf, len, MSG_WAITALL) != len ) goto l_perror;
			buf[len] = 0x00;
			printf( "  %s: %s\n", fields[i], buf );
		} else printf( "  %s is not set.\n", fields[i] );
	}
	
l_close:
	close( mySocket );
	return res;
	
l_perror:
	perror( "error" );
	close( mySocket );
	return 1;
}

/*
request: <IPC_FINGER_EDIT> <ID> <FIELD ID> <LENGTH> [NEW VALUE]
answer: <IPC_SUCCESS> || <IPC_ERROR>
*/
static int editFinger( void ){
	
	int res;
	int id;
	unsigned char field;
	int len;
	unsigned char ans;
	int mySocket;
	
	if( argc < 5 ){
		l_help:
		printf( "Usage: %s %s <id> <field> <new value>\n\nField list:\n", argv[0], argv[1] );
		printf( "  user          (text) Finger owner username\n\n" );
		printf( "  desc          (text) Finger description\n\n" );
		printf( "  threshold     (integer) Minimum match score to accept finger.\n" );
		printf( "                Set 0 to disable finger.\n\n" );
		printf( "  action        (text) Full path to program to execute on finger detect. The\n" );
		printf( "                program will run with the rights of the owner of the \n" );
		printf( "                fingerprint. Left blank to set no action.\n" );
		return 2;
	}
	
	if( sscanf(argv[2], "%i", &id) != 1 ) goto l_help;
	
	if(      !strcmp("user",      argv[3]) ) field = IPC_FIELD_USER;
	else if( !strcmp("desc",      argv[3]) ) field = IPC_FIELD_DESCRIPTION;
	else if( !strcmp("threshold", argv[3]) ) field = IPC_FIELD_THRESHOLD;
	else if( !strcmp("action",    argv[3]) ) field = IPC_FIELD_ACTION;
	else goto l_help;
	
	//calculate length of new value
	len = strlen( argv[4] );
	
	//connect to daemon
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	//send all data
	if( write(mySocket, &IPC_FINGER_EDIT, 1          ) != 1           ) goto l_perror;
	if( write(mySocket, &id,              sizeof(id) ) != sizeof(id)  ) goto l_perror;
	if( write(mySocket, &field,           1          ) != 1           ) goto l_perror;
	if( write(mySocket, &len,             sizeof(len)) != sizeof(len) ) goto l_perror;
	if( write(mySocket, argv[4],          len        ) != len         ) goto l_perror;
	
	//read answer
	if( recv(mySocket, &ans, 1, MSG_WAITALL) != 1 ) goto l_perror;
	
	//print result
	if( ans == IPC_SUCCESS ){
		res = 0;
		printf( "Success.\n" );
	}else if( ans == IPC_DENIED ){
		res = 1;
		printf( "Access denied.\n" );
	} else {
		res = 1;
		printf( "Failed.\n" );
	}
	
	//close socket
	close( mySocket );
	return res;
	
l_perror:
	perror( "error" );
	close( mySocket );
	return 1;
}


/*
request   <IPC_FINGER_TEST> <id>
response: <IPC_FINGER_TEST> <score> || <IPC_ERROR>
          also messages from driver
*/
static int testFinger( void ){
	
	int id;
	int mySocket;
	unsigned char ans;
	int score;
	int res;
	
	if( argc < 3 ){
		l_help:
		printf( "Usage: %s %s <id> \n", argv[0], argv[1] );
		return 2;
	}
	
	if( sscanf(argv[2], "%i", &id) != 1 ) goto l_help;
	
	//connect
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	//send request
	if( write(mySocket, &IPC_FINGER_TEST, 1         ) != 1          ) goto l_perror;
	if( write(mySocket, &id,              sizeof(id)) != sizeof(id) ) goto l_perror;
	
l_recv:
	//read response
	if( recv(mySocket, &ans, 1, MSG_WAITALL) != 1 ) goto l_perror;
	
	//print driver message
	if( ans == IPC_DRIVER_MESSAGE ){
		if( messageFromDriver(mySocket) ) goto l_perror;
		goto l_recv;
	}
	
	res = 1;
	
	//print response
	if( ans == IPC_FINGER_TEST ){
		if( recv(mySocket, &score, sizeof(score), MSG_WAITALL) != sizeof(score) ) goto l_perror;
		res = 0;
		printf( "score: %i\n", score );
	}else if( ans == IPC_DENIED ) printf( "Access denied.\n" );
	else printf( "Operation failed\n" );
	
	close( mySocket );
	return res;
	
l_perror:
	perror( "error" );
	close( mySocket );
	return 1;
}

static int removeFingerprint( void ){
	
	int res;
	int mySocket;
	unsigned int id;
	unsigned char ans;
	
	if( argc < 3 ){
		l_help:
		printf( "Usage: %s %s <id>\n", argv[0], argv[1] );
		return 2;
	}
	
	if( sscanf(argv[2], "%u", &id) != 1 ) goto l_help;
	
	mySocket = connectToDaemon();
	if( mySocket < 0 ) return 1;
	
	if( write(mySocket, &IPC_FINGER_REMOVE, 1         ) != 1          ) goto l_perror;
	if( write(mySocket, &id,                sizeof(id)) != sizeof(id) ) goto l_perror;
	
	if( recv(mySocket, &ans, 1, MSG_WAITALL) != 1 ) goto l_perror;
	
	if( ans ==  IPC_SUCCESS ){
		res = 0;
		printf( "Success.\n" );
	}else if( ans == IPC_DENIED ){
		res = 1;
		printf( "Access denied.\n" );
	} else {
		res = 1;
		printf( "Failed.\n" );
	}
	
	close( mySocket );
	return res;
	
l_perror:
	perror( "error" );
	close( mySocket );
	return 1;
}

/***********************
*         MAIN         *
***********************/

int main( int _argc, char** _argv  ){
	
	argc = _argc;
	argv = _argv;
	
	if( argc < 2 ){
		l_help:
		printf( "Usage: %s <command>\n\nUser commands:\n", argv[0] );
		printf( "  touchpad        Touchpad mode control\n" );
		printf( "  auth            Used to authorize users\n" );
		printf( "  list            Print stored fingerprints\n" );
		printf( "  detect          Scan finger and match him with stored fingerprints\n" );
		printf( "\nAdmin commands:\n" );
		printf( "  add             Add new finger\n");
		printf( "  view <id>       Print detailed info about finger\n");
		printf( "  edit            Edit finger\n" );
		printf( "  test <id>       Test fingerprint to match score\n" );
		printf( "  remove <id>     Remove finger\n");
		return 2;
	}
	
	if( !strcmp("touchpad", argv[1]) ) return touchpad();
	if( !strcmp("auth",     argv[1]) ) return auth();
	if( !strcmp("list",     argv[1]) ) return fingerList();
	if( !strcmp("detect",   argv[1]) ) return fingerDetect();
	if( !strcmp("add",      argv[1]) ) return addNewFingerprint();
	if( !strcmp("view",     argv[1]) ) return viewFinger();
	if( !strcmp("edit",     argv[1]) ) return editFinger();
	if( !strcmp("test",     argv[1]) ) return testFinger();
	if( !strcmp("remove",   argv[1]) ) return removeFingerprint();
	goto l_help;
}
